package com.spring.boot.utils;

import com.alibaba.fastjson.JSONObject;
import com.spring.boot.utils.processor.CommonProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 解析路径工具
 */
public class BeanPath {

    private static final Logger LOG = LoggerFactory.getLogger(BeanPath.class);

    private final char COMMA = '.';
    private final char SINGLE_QUOTE = '\'';
    private final char OPEN_SQUARE_BRACKET = '[';
    private final char CLOSED_SQUARE_BRACKET = ']';
    private final char[] INDEX_CHARS = new char[]{'[', '.'};
    private final String name;
    private BeanPath childBeanPath;
    private String index;


    private BeanPath(String beanPath, boolean rootPath) {
        beanPath = beanPath.trim();
        int beanPathLen = beanPath.length();

        int interceptIndex;

        int tail = 0;

        //判断是否单引号
        if (StringUtils.startsWithChar(beanPath, SINGLE_QUOTE)) {
            interceptIndex = StringUtils.indexOf(beanPath, SINGLE_QUOTE, 1, beanPathLen);
            this.name = beanPath.substring(1, interceptIndex);
            if (interceptIndex + 1 == beanPathLen) {
                return;
            }
            tail = interceptIndex + 1;
        } else {
            interceptIndex = StringUtils.indexOfChars(beanPath, INDEX_CHARS);
            if (interceptIndex == -1) {
                this.name = beanPath;
                return;
            }

            this.name = beanPath.substring(0, interceptIndex);

            tail = interceptIndex;
        }

        if (beanPath.charAt(tail) == COMMA) {
            this.childBeanPath = new BeanPath(beanPath.substring((tail + 1), beanPathLen), false);
        } else if (beanPath.charAt(tail) == OPEN_SQUARE_BRACKET) {
            interceptIndex = StringUtils.indexOf(beanPath, CLOSED_SQUARE_BRACKET);
            if (interceptIndex - tail > 1) {
                this.index = beanPath.substring(tail + 1, interceptIndex);
            }

            if (beanPathLen - interceptIndex > 2) {
                childBeanPath = new BeanPath(beanPath.substring(interceptIndex + 2, beanPathLen), false);
            }
        }
    }


    public static BeanPath compile(String beanPath) {

        return new BeanPath(beanPath, true);
    }

    public Object process(Object bean, Function<Object, Object> function) {
        if (bean == null) {
            return null;
        }

        Set<String> propertySet = this.getPropertySet(bean);
        String property = null;
        Object subBean = null;

        for (Iterator iterator = propertySet.iterator(); iterator.hasNext(); bean = this.setBeanProperty(bean, property, subBean)) {
            property = (String) iterator.next();

            subBean = this.getBeanProperty(bean, property);
            if (this.childBeanPath == null) {
                if (this.index != null) {
                    subBean=this.processArrayProperty(subBean,function);
                } else {
                    subBean = function.apply(subBean);
                }

            } else if (this.index != null) {
                Object value;
                if ("*".equals(this.index)) {
                    List<Object> values = this.getArrayProperty(bean);

                    for (int i = 0; i < values.size(); i++) {
                        value=this.childBeanPath.process(values.get(i),function);
                        this.setArrayProperty(subBean,i,value);
                    }
                }else {
                    Integer ndx = Integer.valueOf(this.index);
                    List<Object> values = this.getArrayProperty(subBean);
                    value=this.childBeanPath.process(values.get(0),function);
                    this.setArrayProperty(subBean,ndx,value);
                }

            } else {
                subBean = this.childBeanPath.process(subBean, function);
            }


        }
        return bean;

    }


    private Object processArrayProperty(Object bean,Function<Object,Object> function){

        Object result=bean;
        if (bean.getClass().isArray()){
            int len;
            if ("*".equals(this.index)){
                len = Array.getLength(bean);

                for (int i = 0; i < len; i++) {
                    Object o = Array.get(bean, i);
                    Array.set(bean,i,function.apply(o));
                }

            }else {
                Integer ndx = Integer.valueOf(this.index);
                Object o = Array.get(bean, ndx);
                Array.set(bean,ndx,function.apply(o));
            }
        }else if (bean instanceof List){
            List<Object> list=(List) bean;
            if ("*".equals(this.index)){
                result = list.stream().map(function::apply).collect(Collectors.toList());
            }else {
                Integer i = Integer.valueOf(this.index);
                Object o = list.get(i);
                list.set(i,function.apply(o));
            }
        }

        return result;
    }


    private Set<String> getPropertySet(Object bean) {
        Set<String> keySet = new HashSet<>();

        String name = this.name;
        switch (name) {
            case "*":
                if ("*".equals(name)) {

                    if (bean instanceof Map) {
                        Map<String, Object> map = (Map) bean;
                        keySet = map.keySet();
                        break;
                    } else {
                        Field[] fields = bean.getClass().getDeclaredFields();

                        for (Field field : fields) {
                            keySet.add(field.getName());
                        }
                        return keySet;
                    }
                }
            default:
                keySet.add(name);

        }

        return keySet;
    }


    private Object getBeanProperty(Object bean, String property) {

        if (!"@".equals(property) && !"$".equals(property)) {
            if (bean instanceof Map) {
                Map<String, Object> map = (Map) bean;
                return map.get(property);
            } else {
                try {
                    Method method = bean.getClass().getMethod("get" + property.substring(0, 1).toUpperCase() + property.substring(1));
                    return method.invoke(bean);
                } catch (Exception e) {
                    LOG.debug("未找到对应getter方法 class={},property={}", bean.getClass(), property);
                    return null;
                }
            }
        }
        return bean;
    }

    private Object setBeanProperty(Object bean, String property, Object value) {
        if (!"@".equals(property) && !"$".equals(property)) {
            if (bean instanceof Map) {
                Map<String, Object> map = (Map) bean;
                map.put(property, value);
            } else {
                try {
                    Method method = bean.getClass().getMethod("set" + property.substring(0, 1).toUpperCase() + property.substring(1), value.getClass());
                    method.invoke(bean, value);
                } catch (Exception e) {
                    LOG.debug("未找到对应setter方法 class={},property={}", bean.getClass(), property);
                }
            }

            return bean;
        }
        return value;
    }


    private List<Object> getArrayProperty(Object bean) {

        List<Object> subList = new ArrayList<>();
        if (bean.getClass().isArray()) {
            if ("*".equals(this.index)) {
                //获取obj对象的长度
                int length = Array.getLength(bean);

                for (int i = 0; i < length; i++) {
                    //根据索引获取对象
                    Object o = Array.get(bean, i);
                    if (o != null) {
                        subList.add(o);
                    }
                }

            } else {
                Object o = Array.get(bean, Integer.valueOf(this.index));
                if (o != null) {
                    subList.add(o);
                }
            }
        } else if (bean instanceof List) {
            List<Object> list = (List) bean;

            if ("*".equals(this.index)) {
                subList.addAll(list);
            } else {
                subList.add(list.get(Integer.valueOf(this.index)));
            }
        }

        return subList;
    }

    private Object setArrayProperty(Object bean,int index,Object value){
        if (bean.getClass().isArray()){
            Array.set(bean,index,value);
        }else if (bean instanceof List){
            List<Object> list=(List)bean;
            list.set(index,value);
        }
        return bean;
    }


    public static void main(String[] args) {

        String str="{\n" +
                "\t\"Test\": {\n" +
                "\t\t\"mysql\": {\n" +
                "\t\t\t\"password\": \"123456789\",\n" +
                "\t\t\t\"user\": \"root\"\n" +
                "\t\t},\n" +
                "\t\t\"myRedis\": {\n" +
                "\t\t\t\"password\": \"123\",\n" +
                "\t\t\t\"user\": \"123\"\n" +
                "\t\t}\n" +
                "\n" +
                "\t}\n" +
                "}";

        Object parse = JSONObject.parse(str);

        BeanPath beanPath = BeanPath.compile("$.'Test'.*.user");
        Object process = beanPath.process(parse, new CommonProcessor());

        System.out.println(JSONObject.toJSON(process));
    }
}
